#include "GameClient.h"
#include "json.hpp"
#include "hlog.h"
#include <cstring>
#include <chrono>
#include <thread>

using json = nlohmann::json;

GameClient::GameClient(std::string ip, uint16_t port) : ip(std::move(ip)), port(port), status(START)
{}

GameClient::~GameClient()
{ Destroy(); }

void GameClient::Create()
{
    auto iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        LOGE("WSAStartup failed with error: %d", iResult);
        return;
    }
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == INVALID_SOCKET) {
        LOGE("socket function failed with error: %ld", WSAGetLastError());
        WSACleanup();
        return;
    }
    struct timeval timeout{};
    timeout.tv_sec = 0;
    timeout.tv_usec = 1000;
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout));
    status = SocketStatus::CREATED;
    LOGI("socket create success!");
}

void GameClient::Connect()
{
    int ret = -1;
    struct sockaddr_in sockaddrIn;
    memset(&sockaddrIn, 0, sizeof(sockaddrIn));
    sockaddrIn.sin_family = AF_INET;
    sockaddrIn.sin_addr.s_addr = inet_addr(ip.c_str());
    sockaddrIn.sin_port = htons(port);
    do {
        ret = connect(fd, (struct sockaddr *) &sockaddrIn, sizeof(sockaddrIn));
    } while ((ret == -1) && (errno == EINTR));
    if (ret < 0) {
        LOGW("connect failed with error: %d", errno);
        return;
    }
    LOGI("connect success!");
    status = SocketStatus::CONNECTED;
}

void GameClient::Disconnect()
{
    shutdown(fd, 2);
    status = SocketStatus::CLOSING;
}

void GameClient::Destroy()
{
    closesocket(fd);
    status = SocketStatus::CLOSED;
}

void GameClient::LinkThread()
{
    long waitTime = 10;
    while (!isStop) {
        switch (status) {
            case SocketStatus::START:
                Create();
                break;
            case CREATED:
                Connect();
                waitTime = 10;
                break;
            case DISCONNECT:
                Disconnect();
                break;
            case CLOSING:
                Destroy();
                break;
            case CLOSED:
                waitTime = 1000; // 等待1s后重连
                status = SocketStatus::START;
                break;
            default:
                break;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(waitTime));
    }
}

bool GameClient::Send(const std::string &data)
{
    if (status != SocketStatus::CONNECTED) {
        LOGE("The socket is not connected. status=%d.", status);
        return false;
    }
    int size = (int) data.size();
    std::string buf;
    buf.append((char *) &size, 4);
    buf.append(data.data(), size);
    int ret = send(fd, buf.data(), size + 4, 0);
    if (ret < 0) {
        if (errno == EPIPE) {
            status = SocketStatus::DISCONNECT;
        }
        LOGE("socket send failed with errno:%d", errno);
        return false;
    }

    if (ret != size + 4) {
        LOGE("socket send failed, data len %d, send success len %d", size, ret);
        return false;
    }
    LOGI("send data: %s", data.c_str());
    return true;
}

bool GameClient::Recv()
{
    if (status != SocketStatus::CONNECTED) {
        LOGE("The socket is not connected. status=%d.", status);
        return false;
    }

    char buffer[1024];
    while (true) {
        memset(buffer, 0, sizeof(buffer));
        ssize_t ret = recv(fd, buffer, sizeof(buffer), 0);
        if (ret > 0) {
            inputBuffer.Append(buffer, ret);
        }
        if (ret == -1 && errno == EINTR) {
            continue;
        }
        if (ret == -1) {
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                LOGE("socket recv failed with errno: %d", errno);
                return false;
            }
            break;
        }
        if (ret == 0) {
            LOGE("connection closed with errno: %d", errno);
            status = SocketStatus::DISCONNECT;
            break;
        }
    }
    return true;
}

void GameClient::Loop()
{
    while (!isStop) {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        if (status != SocketStatus::CONNECTED) {
            continue;
        }
        if (!Recv()) {
            continue;
        }
        ParseRecvMsg();
    }
}

void GameClient::ParseRecvMsg()
{
    std::string message;
    while (inputBuffer.PickMessage(message)) {
        json root = json::parse(message);
        std::string id = root["id"];
        std::string body = root["body"];
        retMap[id] = body;
    }
}